package it.traveldream.beans;

import it.traveldream.dto.ExcursionDTO;
import it.traveldream.dto.HotelDTO;
import it.traveldream.dto.LocationDTO;
import it.traveldream.dto.TPDTO;
import it.traveldream.dto.TransportMeanDTO;
import it.traveldream.entities.Customer;
import it.traveldream.entities.Employee;
import it.traveldream.entities.Excursion;
import it.traveldream.entities.Hotel;
import it.traveldream.entities.Location;
import it.traveldream.entities.TransportMean;
import it.traveldream.entities.TravelPackage;
import it.traveldream.exceptions.InvalidTPException;
import it.traveldream.exceptions.LocationNotFoundException;
import it.traveldream.exceptions.ProductNotFoundException;
import it.traveldream.exceptions.TPNotFoundException;

import java.util.ArrayList;
import java.util.Iterator;
import java.util.List;
import java.util.logging.Logger;

import javax.annotation.security.RolesAllowed;
import javax.ejb.Local;
import javax.ejb.LocalBean;
import javax.ejb.Stateless;
import javax.persistence.EntityManager;
import javax.persistence.PersistenceContext;

/**
 * Session Bean implementation class TPManager
 */
@Stateless
@Local(PackageManagerInterface.class)
@LocalBean
public class TPManager implements PackageManagerInterface {

	@PersistenceContext
	private EntityManager em;
	private Logger log;
    /**
     * Default constructor. 
     */
    public TPManager() {
    	super();
        this.log = Logger.getLogger(this.getClass().getSimpleName());
    }

	@Override
	@RolesAllowed({Employee.role, Customer.role})
	public List<TPDTO> getTPsByLocation(LocationDTO location)
			throws LocationNotFoundException {
		
		Location l = this.em.find(Location.class, location.getLocationId());
		if (l == null)
			throw new LocationNotFoundException();
		
		@SuppressWarnings("unchecked")
		List<TravelPackage> tps = this.em.createNamedQuery("getAllTPInLocation").
				setParameter("location", l).getResultList();
		List<TPDTO> dtos = new ArrayList<>();
		
		for (TravelPackage tp : tps){
			dtos.add(tp.toDTO());
		}
		return dtos;
	}

	@Override
	@RolesAllowed({Employee.role, Customer.role})
	public TPDTO getTP(TPDTO travelPackage) throws TPNotFoundException {
		TravelPackage tp = this.em.find(TravelPackage.class, travelPackage.getTPId());
		
		if (tp == null){
			this.log.severe("ERROR: Package " + travelPackage.getTPId() + " not found");
			throw new TPNotFoundException();
		}
		return tp.toDTO();		
	}

	@Override
	@RolesAllowed({Employee.role})
	public TPDTO saveTP(TPDTO travelPackage) throws ProductNotFoundException,
			InvalidTPException {
		TravelPackage tp = new TravelPackage();
		
		try{
			tp.setName(travelPackage.getName());
			tp.setMean(this.em.find(TransportMean.class, travelPackage.getMean().getProductId()));
			tp.setHotel(this.em.find(Hotel.class, travelPackage.getHotel().getProductId()));
			
			ArrayList<Excursion> excursions = new ArrayList<>();
			for (ExcursionDTO e : travelPackage.getExcursions())
				excursions.add(this.em.find(Excursion.class, e.getProductId()));
			if (excursions.size() > 0)
				tp.setExcursions(excursions);
		} catch (Exception e){
			this.log.severe("ERROR: Product not found: " + e.getMessage());
			throw new ProductNotFoundException();
		}
		
		try{
			this.em.persist(tp);
			this.em.flush();
			return tp.toDTO();
		} catch (Exception e){
			this.log.severe("ERROR: Saving TP: " + e.getMessage());
			throw new InvalidTPException();
		}
		
	}

	@Override
	@RolesAllowed({Employee.role})
	public void updateTP(TPDTO travelPackage) throws TPNotFoundException,
			ProductNotFoundException, InvalidTPException {
		
		try{
			TravelPackage tp = this.em.find(TravelPackage.class, travelPackage.getTPId());
			if (tp == null){
				this.log.severe("ERROR: TP " + travelPackage.getTPId() +" not found");
				throw new TPNotFoundException();
			}
			
			this.setMean(travelPackage, travelPackage.getMean());
			this.setHotel(travelPackage, travelPackage.getHotel());

			ArrayList<ExcursionDTO> toRemove = new ArrayList<>();
			for (Excursion e : tp.getExcursions())
				toRemove.add(e.toDTO());
			ArrayList<ExcursionDTO> toAdd = new ArrayList<>(travelPackage.getExcursions());

			toAdd.removeAll(toRemove);
			toRemove.removeAll(travelPackage.getExcursions());
			
			for (ExcursionDTO e : toAdd)
				this.addExcursion(travelPackage, e);
			for (ExcursionDTO e : toRemove)
				this.removeExcursion(travelPackage, e);
			
			tp.setName(travelPackage.getName());
			this.em.merge(tp);
			this.em.flush();
		} catch (RuntimeException e){
			this.log.severe("ERROR: " + e.getMessage());
			throw new RuntimeException();
		}
	}

	@Override
	@RolesAllowed({Employee.role})
	public void deleteTP(TPDTO travelPackage) throws TPNotFoundException {
		TravelPackage tp = this.em.find(TravelPackage.class, travelPackage.getTPId());
		if (tp == null){
			this.log.severe("ERROR: TP " + travelPackage.getTPId() +" not found");
			throw new TPNotFoundException();
		}
		
		try{
			this.em.remove(tp);
			this.em.flush();
		} catch (Exception e){
			this.log.severe("ERROR: Problem while removing package " + travelPackage.getTPId());
			throw new RuntimeException();
		}	
	}

	@Override
	@RolesAllowed({Employee.role, Customer.role})
	public List<TransportMeanDTO> getAlternativeMeans(TPDTO travelPackage)
			throws TPNotFoundException {
		TravelPackage tp = this.em.find(TravelPackage.class, travelPackage.getTPId());
		if (tp == null){
			this.log.severe("ERROR: TP not found: " + travelPackage.getTPId());
			throw new TPNotFoundException();
		}
		
		try{
			@SuppressWarnings("unchecked")
			List<TransportMean> means = this.em.createNamedQuery("getAlternativeMeanToLocation").
						setParameter("location", tp.getMean().getLocation()).
						setParameter("id", travelPackage.getMean().getProductId()).
						getResultList();
			
			ArrayList<TransportMeanDTO> dto = new ArrayList<>();
			for (TransportMean m : means)
				if (! m.getMeanType().getName().contains("BACK"))
					dto.add(m.toDTO());
			return dto;
		} catch (Exception e){
			this.log.severe("ERROR: " + e.getMessage());
			throw new RuntimeException();
		}
	}

	@Override
	@RolesAllowed({Employee.role, Customer.role})
	public List<HotelDTO> getAlternativeHotels(TPDTO travelPackage)
			throws TPNotFoundException {
		try{
			TravelPackage tp = this.em.find(TravelPackage.class, travelPackage.getTPId());
			if (tp == null){
				this.log.severe("ERROR: TP not found: " + travelPackage.getTPId());
				throw new TPNotFoundException();
			}
			
			@SuppressWarnings("unchecked")
			List<Hotel> hotels = this.em.createNamedQuery("getAlternativeHotelInLocation").
						setParameter("location", tp.getMean().getLocation()).
						setParameter("id", travelPackage.getHotel().getProductId()).
						getResultList();
			
			ArrayList<HotelDTO> dto = new ArrayList<>();
			for (Hotel h : hotels)
				dto.add(h.toDTO());
			return dto;
		} catch (Exception e){
			this.log.severe("ERROR: " + e.getMessage());
			throw new RuntimeException();
		}
	}

	@Override
	@RolesAllowed({Employee.role, Customer.role})
	@SuppressWarnings("unchecked")
	public List<ExcursionDTO> getAlternativeExcursions(TPDTO travelPackage)
			throws TPNotFoundException {
		try{
			TravelPackage tp = this.em.find(TravelPackage.class, travelPackage.getTPId());
			if (tp == null){
				this.log.severe("ERROR: TP not found: " + travelPackage.getTPId());
				throw new TPNotFoundException();
			}
			
			List<Long> idList = new ArrayList<>();
			for (ExcursionDTO ex : travelPackage.getExcursions())
				idList.add(ex.getProductId());
			
			List<Excursion> excursions = null;
			if (idList.size() == 0){ // no excursions in TP
				excursions = this.em.createNamedQuery("getAllExcursionsInLocation").
						setParameter("location", tp.getMean().getLocation()).
						getResultList();
			} else {
				excursions = this.em.createNamedQuery("getAlternativeExcursionInLocation").
						setParameter("location", tp.getMean().getLocation()).
						setParameter("idList", idList).
						getResultList();
			}
			
			ArrayList<ExcursionDTO> dto = new ArrayList<>();
			for (Excursion e : excursions)
				dto.add(e.toDTO());
			return dto;
		} catch (Exception e){
			this.log.severe("ERROR: " + e.getMessage());
			throw new RuntimeException();
		}
	}

	@Override
	@RolesAllowed({Employee.role})
	public void setMean(TPDTO travelPackage, TransportMeanDTO mean)
			throws TPNotFoundException, ProductNotFoundException {
		try{
			TravelPackage tp = this.em.find(TravelPackage.class, travelPackage.getTPId());
			if (tp == null){
				this.log.severe("ERROR: TP " + travelPackage.getTPId() +" not found");
				throw new TPNotFoundException();
			}
			
			try{
				tp.setMean(this.em.find(TransportMean.class, mean.getProductId()));
				this.em.merge(tp);
				this.em.flush();
			} catch (Exception e){
				this.log.severe("ERROR: Mean not found: " + e.getMessage());
				throw new ProductNotFoundException();
			}
		} catch (Exception e){
			this.log.severe("ERROR: " + e.getMessage());
			throw new RuntimeException();
		}
		
		
	}

	@Override
	@RolesAllowed({Employee.role})
	public void setHotel(TPDTO travelPackage, HotelDTO hotel)
			throws TPNotFoundException, ProductNotFoundException {
		try{
			TravelPackage tp = this.em.find(TravelPackage.class, travelPackage.getTPId());
			if (tp == null){
				this.log.severe("ERROR: TP " + travelPackage.getTPId() +" not found");
				throw new TPNotFoundException();
			}
			
			try{
				tp.setHotel(this.em.find(Hotel.class, travelPackage.getHotel().getProductId()));
				this.em.merge(tp);
				this.em.flush();
			} catch (Exception e){
				this.log.severe("ERROR: Hotel not found: " + e.getMessage());
				throw new ProductNotFoundException();
			}
		} catch (Exception e){
			this.log.severe("ERROR: " + e.getMessage());
			throw new RuntimeException();
		}
	}

	@Override
	@RolesAllowed({Employee.role})
	public void addExcursion(TPDTO travelPackage, ExcursionDTO excursion)
			throws TPNotFoundException, ProductNotFoundException {
		try{
			TravelPackage tp = this.em.find(TravelPackage.class, travelPackage.getTPId());
			if (tp == null){
				this.log.severe("ERROR: TP " + travelPackage.getTPId() +" not found");
				throw new TPNotFoundException();
			}
			
			try{
				Excursion e = this.em.find(Excursion.class, excursion.getProductId());
				List<Excursion> exs = new ArrayList<Excursion>(tp.getExcursions());
				exs.add(e);
				tp.setExcursions(exs);
				
				this.em.merge(tp);
				this.em.flush();
			} catch (Exception e){
				this.log.severe("ERROR: Excursion not found: " + e.getMessage());
				throw new ProductNotFoundException();
			}
		} catch (Exception e){
			this.log.severe("ERROR: " + e.getMessage());
			throw new RuntimeException();
		}
	}

	@Override
	@RolesAllowed({Employee.role})
	public void removeExcursion(TPDTO travelPackage, ExcursionDTO excursion)
			throws TPNotFoundException, ProductNotFoundException {
		try{
			TravelPackage tp = this.em.find(TravelPackage.class, travelPackage.getTPId());
			if (tp == null){
				this.log.severe("ERROR: TP " + travelPackage.getTPId() +" not found");
				throw new TPNotFoundException();
			}
			
			try{
				Excursion e = this.em.find(Excursion.class, excursion.getProductId());
				List<Excursion> exs = new ArrayList<Excursion>(tp.getExcursions());
				Iterator<Excursion> i = exs.iterator();
				while (i.hasNext()){
					if (e.getProductId().equals(i.next().getProductId()))
							i.remove();
				}
				tp.setExcursions(exs);
				
				this.em.merge(tp);
				this.em.flush();
			} catch (Exception e){
				this.log.severe("ERROR: Excursion not found: " + e.getMessage());
				throw new ProductNotFoundException();
			}
		} catch (Exception e){
			this.log.severe("ERROR: " + e.getMessage());
			throw new RuntimeException();
		}
	}
}
